package com.zbs.framework.security.filter;

import com.zbs.framework.security.config.FrameworkConstants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.log.LogMessage;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Collection;

/**
 * 动态权限过滤器，用于实现基于路径的动态权限过滤
 * Created by macro on 2020/2/7.
 */
@Configuration
public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter, ApplicationEventPublisherAware {

    @Autowired
    private  DynamicSecurityMetadataSource dynamicSecurityMetadataSource;
    @Autowired
    private  DynamicSecurityRunAsManager dynamicSecurityRunAsManager;

    public DynamicSecurityFilter(@Autowired DynamicAccessDecisionManager dynamicAccessDecisionManager) {
        super.setAccessDecisionManager(dynamicAccessDecisionManager);
    }


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        final String token = request.getHeader(FrameworkConstants.TOKEN_NAME);
        final String sign = request.getHeader(FrameworkConstants.SIGN_NAME);
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        //OPTIONS请求直接放行
        if(request.getMethod().equals(HttpMethod.OPTIONS.toString())){
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            return;
        }
        //白名单请求直接放行
        PathMatcher pathMatcher = new AntPathMatcher();
        for (String path : FrameworkConstants.IGNORING_URL) {
            if(pathMatcher.match(path,request.getRequestURI())){
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
                return;
            }
        }
        // 改变 权限
        Authentication authentication = this.dynamicSecurityRunAsManager.buildRunAs(token,sign, authentication(),fi);
        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        //此处会调用AccessDecisionManager中的decide方法进行鉴权操作
        InterceptorStatusToken interceptorStatusToken = this.beforeInvocation(fi);
        if (interceptorStatusToken == null) {
            Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(fi);
            this.attemptAuthorization(fi,attributes,authentication);
        }
        try {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(interceptorStatusToken, null);
        }
    }

    private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) {
        try {
            super.getAccessDecisionManager().decide(authenticated, object, attributes);
        } catch (AccessDeniedException var5) {
            if (this.logger.isTraceEnabled()) {
                this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, super.getAccessDecisionManager()));
            } else if (this.logger.isDebugEnabled()) {
                this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes));
            }
            throw var5;
        }
    }


    private Authentication authentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }


    @Override
    public void destroy() {
    }
 
    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }
 
    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return dynamicSecurityMetadataSource;
    }
 
}